﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Net;
using System.Text;
using System.Xml;
using System.Xml.XPath;
using digiTwitter.Core;

namespace digiTwitter
{
    public class TwitterSearch:TwitterBase<TwitterSearch>
    {
        private const string BASE_SEARCH_URL = "http://search.twitter.com/search.{0}";
        private const string BASE_USERS_URL = "http://twitter.com/users/show/{0}.xml";

        #region Public Methods
    
        /// <summary>
        /// Returns a list of Users according to the search criteria.
        /// </summary>
        /// <param name="searchSettings"></param>
        /// <param name="detailedSearch">If true, it will get each user's complete information. If false, it will get only the user name</param>
        /// <returns></returns>
        public List<TwitterUser> SearchUsers(TwitterSearchSettings searchSettings, bool detailedSearch)
        {
            XmlDocument searchResults = SearchAsAtom(searchSettings);
            
            XPathNavigator navigator = searchResults.ChildNodes[1].CreateNavigator();
            XPathNodeIterator iterator = navigator.SelectChildren("entry", "http://www.w3.org/2005/Atom");
            TwitterUser user;
            string userName;
            var usersList = new List<TwitterUser>();
        
            while(iterator.MoveNext())
            {
                if (iterator.Current.MoveToChild("title", "http://www.w3.org/2005/Atom"))
                {
                    userName = iterator.Current.Value.Split(':')[0];

                    user = detailedSearch ? new TwitterUser { UserName = userName } : GetUser(userName);

                    usersList.Add(user);
                    iterator.Current.MoveToParent();
                }
            }

            return usersList;
        }

        /// <summary>
        /// Returns a user from a given username
        /// </summary>
        /// <param name="userName"></param>
        /// <returns></returns>
        public TwitterUser GetUser(string userName)
        {
            TwitterUser user = null;

            if(!String.IsNullOrEmpty(userName))
            {
                string requestURL = string.Format(BASE_USERS_URL, userName);
                string results = HTTPRequester.GetResponseString(requestURL);
                var searchResults = new XmlDocument();

                if (!String.IsNullOrEmpty(results))
                {
                    searchResults.LoadXml(results);
                    user = new TwitterUser(searchResults);
                }
            }

            return user;
        }

        /// <summary>
        /// Performs a Search inside the Twitter records.
        /// </summary>
        /// <param name="searchSettings"></param>
        /// <returns></returns>
        public XmlDocument SearchAsAtom(TwitterSearchSettings searchSettings)
        {
            var searchResults = new XmlDocument();
            String requestURL = BuildSearchUrl(searchSettings,Enums.OutputFormat.Atom);
            string results = HTTPRequester.GetResponseString(requestURL);

            if (!String.IsNullOrEmpty(results))
                searchResults.LoadXml(results);

            return searchResults;
        }

        /// <summary>
        /// Performs a search and returns a JSON response.
        /// </summary>
        /// <param name="searchSettings"></param>
        /// <returns></returns>
        public string SearchAsJSON(TwitterSearchSettings searchSettings)
        {
            return GetResponseString(BuildSearchUrl(searchSettings, Enums.OutputFormat.JSON));
        }

        /// <summary>
        /// Performs a search and returns a list of typed objects.
        /// </summary>
        /// <param name="searchSettings"></param>
        /// <returns></returns>
        public List<TwitterSearchResult> Search(TwitterSearchSettings searchSettings)
        {
            XmlDocument searchResults = SearchAsAtom(searchSettings);

            XPathNavigator navigator = searchResults.ChildNodes[1].CreateNavigator();
            XPathNodeIterator iterator = navigator.SelectChildren("entry", "http://www.w3.org/2005/Atom");
            TwitterSearchResult result;

            var results = new List<TwitterSearchResult>();
            while (iterator.MoveNext())
            {
                if (iterator.Current is IHasXmlNode)
                    results.Add(new TwitterSearchResult(((IHasXmlNode)iterator.Current).GetNode()));
            }

            return results;
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Generates a GET request to a given URL and return the results as String.
        /// </summary>
        /// <param name="requestURL"></param>
        /// <returns></returns>
        private string GetResponseString(String requestURL)
        {
            using (var client = new WebClient())
            {
                client.Credentials = GetCredentials();

                using (Stream stream = client.OpenRead(requestURL))
                {
                    using (var reader = new StreamReader(stream))
                    {
                       return reader.ReadToEnd();
                    }
                }
            }
        }

        /// <summary>
        /// Retrieves the credentials from the Web.config keys: TwitterUsername and TwitterPassword.
        /// This is useful if you need to exceed the maximun limit of request per hour (100).
        /// You will also need to request to be withelisted: http://twitter.com/help/request_whitelisting
        /// </summary>
        /// <returns></returns>
        private ICredentials GetCredentials()
        {
         
            if(!String.IsNullOrEmpty(ConfigurationManager.AppSettings["TwitterUsername"]) &&
                !String.IsNullOrEmpty(ConfigurationManager.AppSettings["TwitterPassword"]))
            {
                return new NetworkCredential(ConfigurationManager.AppSettings["TwitterUsername"],
                                             ConfigurationManager.AppSettings["TwitterPassword"]);
            }

            return CredentialCache.DefaultNetworkCredentials;
        }

        /// <summary>
        /// Builds the request URL with all the needed parameters from a searchsettings object.
        /// </summary>
        /// <param name="searchSettings"></param>
        /// <returns></returns>
        private String BuildSearchUrl(TwitterSearchSettings searchSettings,Enums.OutputFormat outputFormat)
        {
            var requestURL = new StringBuilder();

            requestURL.AppendFormat(BASE_SEARCH_URL, outputFormat.ToString().ToLower());
            
            bool isFirstParam = true;

            if (!String.IsNullOrEmpty(searchSettings.Language))
            {
                requestURL.AppendFormat("?lang={0}", searchSettings.Language);
                isFirstParam = false;
            }

            if (searchSettings.ReturnPerPage > 0)
            {
                if (isFirstParam)
                {
                    requestURL.AppendFormat("?rpp={0}", searchSettings.ReturnPerPage);
                    isFirstParam = false;
                }
                else
                    requestURL.AppendFormat("&rpp={0}", searchSettings.ReturnPerPage);
            }

            if (searchSettings.Page > 0)
            {
                if (isFirstParam)
                {
                    requestURL.AppendFormat("?page={0}", searchSettings.Page);
                    isFirstParam = false;
                }
                else
                    requestURL.AppendFormat("&page={0}", searchSettings.Page);
            }

            if (searchSettings.SinceID > 0)
            {
                if (isFirstParam)
                {
                    requestURL.AppendFormat("?since_id={0}", searchSettings.SinceID);
                    isFirstParam = false;
                }
                else
                    requestURL.AppendFormat("&since_id={0}", searchSettings.SinceID);
            }

            if (!String.IsNullOrEmpty(searchSettings.GeoCode))
            {
                if (isFirstParam)
                {
                    requestURL.AppendFormat("?geocode={0}", searchSettings.GeoCode);
                    isFirstParam = false;
                }
                else
                    requestURL.AppendFormat("&geocode={0}", searchSettings.GeoCode);
            }

            if (searchSettings.ShowUser)
            {
                if (isFirstParam)
                {
                    requestURL.Append("?show_user=true");
                    isFirstParam = false;
                }
                else
                    requestURL.Append("&show_user=true");
            }

            if (!String.IsNullOrEmpty(searchSettings.Query))
            {
                if (isFirstParam)
                    requestURL.AppendFormat("?q={0}", searchSettings.Query);
                else
                    requestURL.AppendFormat("&q={0}", searchSettings.Query);
            }

            return requestURL.ToString();
        }

        #endregion
    }
}
